﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using KBEngine.Setting.TypeDef;

namespace KBEngine.Setting
{
    [Serializable]
    [XmlRoot("root")]
    public class Types : IXmlSerializable
    {
        public List<BaseType> AllTypes;
        public const string FIXED_DICT = "FIXED_DICT";
        public const string ARRAY_HEAD = "ARRAY|"; //在Entity中出现Array时, 将增加前缀 ARRAY_HEAD

        public IType GetType(string typeStr)
        {
            bool isArray = false;
            if (typeStr.StartsWith(ARRAY_HEAD))
            {
                isArray = true;
                typeStr = typeStr.Substring(ARRAY_HEAD.Length);
            }
            var iType = AllTypes.Find(t => t.TypeName == typeStr);
            if (!isArray)
                return iType;
            else
                return new ArrayType(iType);
        }
        public string GetTypeStr(IType itype)
        {
            if (itype.GetType() == typeof(ArrayType))
                return ARRAY_HEAD + ((ArrayType)itype).ElementType.TypeName;
            else
                return itype.TypeName;
        }

        #region XmlSerializable
        private void InitBaseType()
        {
            AllTypes = new List<BaseType>();
            AllTypes.Add(new BaseType("UINT8"));
            AllTypes.Add(new BaseType("UINT16"));
            AllTypes.Add(new BaseType("UINT32"));
            AllTypes.Add(new BaseType("UINT64"));

            AllTypes.Add(new BaseType("INT8"));
            AllTypes.Add(new BaseType("INT16"));
            AllTypes.Add(new BaseType("INT32"));
            AllTypes.Add(new BaseType("INT64"));

            AllTypes.Add(new BaseType("FLOAT"));
            AllTypes.Add(new BaseType("DOUBLE"));

            AllTypes.Add(new BaseType("VECTOR2"));
            AllTypes.Add(new BaseType("VECTOR3"));
            AllTypes.Add(new BaseType("VECTOR4"));

            AllTypes.Add(new BaseType("STRING"));
            AllTypes.Add(new BaseType("UNICODE"));
            AllTypes.Add(new BaseType("PYTHON"));
            AllTypes.Add(new BaseType("PY_DICT"));
            AllTypes.Add(new BaseType("PY_TUPLE"));
            AllTypes.Add(new BaseType("PY_LIST"));
            AllTypes.Add(new BaseType("MAILBOX"));
            AllTypes.Add(new BaseType("BLOB"));
            AllTypes.Add(new BaseType("ENTITYCALL"));
        }
        private IType GetIType(XmlNode xmlNode)
        {
            if (xmlNode.FirstChild.InnerText.Trim() != "ARRAY")
                return AllTypes.Find(e => e.TypeName == xmlNode.InnerText.Trim());
            else
                return new ArrayType(GetIType(xmlNode.SelectSingleNode("of")));
        }
        private string GetXmlText(ArrayType arrayType)
        {
            var array = arrayType.ElementType as ArrayType;
            if (array != null)
                return string.Format("ARRAY <of> {0} </of>", GetXmlText(array));

            return string.Format("ARRAY <of> {0} </of>", arrayType.ElementType.TypeName);
        }

        public XmlSchema GetSchema() => null;
        public void ReadXml(XmlReader reader)
        {
            InitBaseType();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(reader);
            var root = xmlDoc.SelectSingleNode("root");
            foreach (XmlNode xmlNode in root.ChildNodes)
            {
                //别名类型
                if (xmlNode.FirstChild.InnerText.Trim() != FIXED_DICT)
                {
                    AllTypes.Add(new AliasType(xmlNode.Name, GetIType(xmlNode)));
                    continue;
                }

                //自定义类型
                var userType = new UserType(xmlNode.Name);
                var implNode = xmlNode.SelectSingleNode("implementedBy");
                if (implNode != null)
                    userType.ImplementedBy = implNode.InnerText.Trim();

                foreach (XmlNode item in xmlNode.SelectSingleNode("Properties"))
                {
                    var field = new UserType.Field();
                    field.FieldName = item.Name.Trim();
                    field.Type = GetIType(item.SelectSingleNode("Type"));
                    var perNode = item.SelectSingleNode("Persistent");
                    if (perNode != null)
                        field.persistent = bool.Parse(perNode.InnerText.Trim());
                    var databaseNode = item.SelectSingleNode("DatabaseLength");
                    if (databaseNode != null)
                        field.databaseLength = uint.Parse(databaseNode.InnerText.Trim());
                    userType.Properties.Add(field);
                }
                AllTypes.Add(userType);
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("root");
            xmlDoc.AppendChild(root);
            foreach (var item in AllTypes)
            {
                var type = item.GetType();
                if (type == typeof(BaseType))
                    continue;

                if (type == typeof(AliasType))
                {
                    var aliasType = (AliasType)item;
                    var node = xmlDoc.CreateElement(aliasType.TypeName);
                    if (aliasType.RealType is BaseType)
                        node.InnerText = aliasType.RealType.TypeName;
                    else
                    {
                        var array = aliasType.RealType as ArrayType;
                        if (array == null)
                            throw new InvalidOperationException();

                        node.InnerXml = GetXmlText(array);
                    }
                    root.AppendChild(node);
                    continue;
                }

                if (type == typeof(UserType))
                {
                    var userType = (UserType)item;
                    var node = xmlDoc.CreateElement(userType.TypeName);
                    node.InnerText = FIXED_DICT;
                    root.AppendChild(node);
                    if (!string.IsNullOrEmpty(userType.ImplementedBy))
                    {
                        var impl = xmlDoc.CreateElement("implementedBy");
                        impl.InnerText = userType.ImplementedBy;
                        node.AppendChild(impl);
                    }
                    var nodeProper = xmlDoc.CreateElement("Properties");
                    foreach (var proper in userType.Properties)
                    {
                        //type
                        var varNode = xmlDoc.CreateElement(proper.FieldName);
                        var typeNode = xmlDoc.CreateElement("Type");
                        if (proper.Type is BaseType)
                            typeNode.InnerText = proper.Type.TypeName;
                        else
                        {
                            var array = proper.Type as ArrayType;
                            if (array == null)
                                throw new InvalidOperationException();
                            typeNode.InnerXml = GetXmlText(array);
                        }
                        varNode.AppendChild(typeNode);

                        //persistent
                        if (!proper.persistent)
                        {
                            var perNode = xmlDoc.CreateElement("Persistent");
                            perNode.InnerText = proper.persistent.ToString().ToLower();
                            varNode.AppendChild(perNode);
                        }

                        //databaseLength
                        if (proper.databaseLength.HasValue && proper.databaseLength != 0)
                        {
                            var databaseNode = xmlDoc.CreateElement("DatabaseLength");
                            databaseNode.InnerText = proper.databaseLength.ToString();
                            varNode.AppendChild(databaseNode);
                        }

                        nodeProper.AppendChild(varNode);
                    }
                    node.AppendChild(nodeProper);
                    continue;
                }
                throw new InvalidCastException(type.Name);
            }
            foreach (XmlNode item in root)
            {
                item.WriteTo(writer);
            }
        }
        #endregion
    }
}
